package com.artnology.independence.model;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;

import com.artnology.independence.Activator;
import com.artnology.independence.model.relationships.DVExtensionRelationship;
import com.artnology.independence.model.relationships.DVImplementsRelationship;
import com.artnology.independence.model.relationships.DVOneToManyRelationship;
import com.artnology.independence.model.relationships.DVOneToOneRelationship;
import com.artnology.independence.model.relationships.DVPackageRelationship;
import com.artnology.independence.model.relationships.DVReference;
import com.artnology.independence.model.relationships.DVUsesRelationship;
import com.artnology.independence.reengineering.DVNodePattern;
import com.artnology.independence.views.DependencyView;
import com.artnology.independence.views.IGraphModificationListener;
import com.artnology.independence.views.LogicalOperator;

import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.EdgeType;

public class DependencyGraph {

	private Graph<DVNode, DVReference> graphInstance;
	private DVGraphModel graphModel = null;
	private Set<DVNodePattern> allowedTypePatterns = new HashSet<DVNodePattern>();
	private Set<IGraphModificationListener> graphModificationListeners = new HashSet<IGraphModificationListener>();

	public DependencyGraph(DVGraphModel graphModel) {
		super();
		this.graphModel = graphModel;
		this.graphModel.setMyGraph(this);
		Collection<DVNode> initialNodes = new HashSet<DVNode>();
		initialNodes.addAll(graphModel.getAllTypes());
		refreshGraph(initialNodes);
	}

	public void addGraphModificationListener(IGraphModificationListener listener) {
		graphModificationListeners.add(listener);
	}

	public void fireGraphModificationEvent(DependencyGraph modifiedGraph) {
		for (IGraphModificationListener listener : graphModificationListeners) {
			listener.graphUpdated(modifiedGraph);
		}
	}

	public DVGraphModel getGraphModel() {
		return graphModel;
	}

	public Graph<DVNode, DVReference> getGraph() {
		return graphInstance;
	}

	public void setGraph(Graph<DVNode, DVReference> newGraph) {
		graphInstance = newGraph;
		fireGraphModificationEvent(this);
	}

	public void addNodesWithReferences(Collection<DVNode> nodesToAdd) {
		for (DVNode node : nodesToAdd) {
			getGraph().addVertex(node);
		}

		// firstly add ALL references for the added nodes
		for (DVNode node : nodesToAdd) {
			for (DVReference ref : node.getAllIncidentReferences()) {
				// make sure only visible nodes will be connected, otherwise
				// jung automatically adds missing nodes to the graph!!
				if (getGraph().containsVertex(ref.getReferenceFromNode())
						&& getGraph().containsVertex(ref.getReferencedNode())) {
					getGraph().addEdge(ref, ref.getReferenceFromNode(),
							ref.getReferencedNode(), EdgeType.DIRECTED);
				}

			}
		}
		
		
		

		// and now filter graph according to current filter settings
		Set<Class<? extends DVReference>> filterReferenceTypes = new HashSet<Class<? extends DVReference>>();
		setupFilterForReferenceType(filterReferenceTypes,
				DependencyView.FILTER_EXTENDS_RELATIONSHIPS,
				DVExtensionRelationship.class);
		setupFilterForReferenceType(filterReferenceTypes,
				DependencyView.FILTER_IMPLEMENTS_RELATIONSHIPS,
				DVImplementsRelationship.class);
		setupFilterForReferenceType(filterReferenceTypes,
				DependencyView.FILTER_ONE_TO_MANY_RELATIONSHIPS,
				DVOneToManyRelationship.class);
		setupFilterForReferenceType(filterReferenceTypes,
				DependencyView.FILTER_ONE_TO_ONE_RELATIONSHIPS,
				DVOneToOneRelationship.class);
		setupFilterForReferenceType(filterReferenceTypes,
				DependencyView.FILTER_USAGE_RELATIONSHIPS,
				DVUsesRelationship.class);

		setGraph(DVReference.getEdgePredicateFilter(filterReferenceTypes)
				.transform(getGraph()));

	}

	private void setupFilterForReferenceType(
			Set<Class<? extends DVReference>> filterReferenceTypes,
			String referenceTypeString,
			Class<? extends DVReference> referenceTypeClass) {

		if (Activator.getDefault().getPrefStore()
				.getBoolean(referenceTypeString)) {
			filterReferenceTypes.add(referenceTypeClass);
		}
	}
  
	public void refreshGraph(Collection<DVNode> allNodes) {
		graphInstance = new DirectedSparseMultigraph<DVNode, DVReference>();
		for (DVNode dvNode : allNodes) {
			graphInstance.addVertex(dvNode);
		}
		for (DVNode dvNode : allNodes) {
			for (DVReference reference : dvNode.getAllIncidentReferences()) {
				if (!graphInstance.containsVertex(reference.getReferencedNode())) {
					graphInstance.addVertex(reference.getReferencedNode());
				}
				if (!graphInstance.containsVertex(reference.getReferenceFromNode())) {
					graphInstance.addVertex(reference.getReferenceFromNode());
				}
				
			
				graphInstance.addEdge(reference, reference.getReferenceFromNode(),
							reference.getReferencedNode(),
							EdgeType.DIRECTED);

			}
		}
		fireGraphModificationEvent(this);
	}

	public void filterGraph() {

		if (allowedTypePatterns.size() > 0) {

			// filter allNodes for allowed pattern
			Matcher matcher = null;
			String candidateName = null;
			Set<DVNode> rememberAddedNodes = new HashSet<DVNode>();
			for (DVNode dvNode : graphModel.getAllTypes()) {
				if (dvNode instanceof DVType) {
					candidateName = ((DVType) dvNode).getFullyQualifiedName();
				} else {
					candidateName = dvNode.getName();
				}
				boolean matched = false;
				for (DVNodePattern dvNodePattern : allowedTypePatterns) {
					matcher = dvNodePattern.getTypePattern().matcher(
							candidateName.toLowerCase());
					if (matcher.find()) {
						matched = true;
					}
				}
				if (!matched && graphInstance.containsVertex(dvNode)) {

					graphInstance.removeVertex(dvNode);
				} else if (!graphInstance.containsVertex(dvNode) && matched) {

					rememberAddedNodes.add(dvNode);
				}
			}

			addNodesWithReferences(rememberAddedNodes);
		}
	}

	public void addTypePatternString(String newTypePatternString) {
		allowedTypePatterns.add(new DVNodePattern(newTypePatternString,
				LogicalOperator.OR));
	}

	public void resetTypePatterns() {
		allowedTypePatterns.clear();
	}



}
